//
//  j3RenderingEngine2.cpp
//  HJC3D
//
//  Created by HJC on 11-10-10.
//  Copyright 2011年 __MyCompanyName__. All rights reserved.
//

#include "j3RenderingEngine2.h"
#include "j3FilePath.h"
#include <iostream>
#include <cmath>



static const float AnimationDuration = 0.25;

extern j3RenderingEngine* CreateRenderer2()
{
    return new j3RenderingEngine2();
}



j3RenderingEngine2::j3RenderingEngine2()
{
    glGenRenderbuffers(1, &_colorRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
}


void j3RenderingEngine2::initConeData()
{
    const float coneRadius = 0.5f;
    const float coneHeight = 1.866f;
    const int coneSlices = 40;
    
    {
        // Allocate space for the cone vertices.
        _cone.resize((coneSlices + 1) * 2);
        
        // Initialize the vertices of the triangle strip.
        std::vector<Vertex>::iterator vertex = _cone.begin();
        const float dtheta = 2 * M_PI / coneSlices;
        for (float theta = 0; vertex != _cone.end(); theta += dtheta) {
            
            // Grayscale gradient
            float brightness = std::abs(std::sin(theta));
            vec4 color(brightness, brightness, brightness, 1);
            
            // Apex vertex
            vertex->Position = vec3(0, 1, 0);
            vertex->Color = color;
            vertex++;
            
            // Rim vertex
            vertex->Position.x = coneRadius * cos(theta);
            vertex->Position.y = 1 - coneHeight;
            vertex->Position.z = coneRadius * sin(theta);
            vertex->Color = color;
            vertex++;
        }
    }
    
    {
        // Allocate space for the disk vertices.
        _disk.resize(coneSlices + 2);
        
        // Initialize the center vertex of the triangle fan.
        std::vector<Vertex>::iterator vertex = _disk.begin();
        vertex->Color = vec4(0.75, 0.75, 0.75, 1);
        vertex->Position.x = 0;
        vertex->Position.y = 1 - coneHeight;
        vertex->Position.z = 0;
        vertex++;
        
        // Initialize the rim vertices of the triangle fan.
        const float dtheta = 2 * M_PI / coneSlices;
        for (float theta = 0; vertex != _disk.end(); theta += dtheta) {
            vertex->Color = vec4(0.75, 0.75, 0.75, 1);
            vertex->Position.x = coneRadius * cos(theta);
            vertex->Position.y = 1 - coneHeight;
            vertex->Position.z = coneRadius * sin(theta);
            vertex++;
        }
    }
}


void j3RenderingEngine2::initialize(int width, int height)
{
    this->initConeData();
    
    // Create the depth buffer.
    glGenRenderbuffers(1, &_depthRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER,
                             GL_DEPTH_COMPONENT16,
                             width,
                             height);
    
    // Create the framebuffer object; attach the depth and color buffers.
    glGenFramebuffers(1, &_framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,
                                 GL_COLOR_ATTACHMENT0,
                                 GL_RENDERBUFFER,
                                 _colorRenderbuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,
                                 GL_DEPTH_ATTACHMENT,
                                 GL_RENDERBUFFER,
                                 _depthRenderbuffer);
    
    // Bind the color buffer for rendering.
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
    
    glViewport(0, 0, width, height);
    glEnable(GL_DEPTH_TEST);
    
    std::string vertexShader = j3ResourceContent("Simple.vert");
    std::string fragmentShader = j3ResourceContent("Simple.frag");
    
    _simpleProgram = buildProgram(vertexShader.c_str(), fragmentShader.c_str());
    
    glUseProgram(_simpleProgram);
    

    
    GLint projectionUniform = glGetUniformLocation(_simpleProgram, "Projection");
    mat4 projectionMatrix = mat4::frustum(-1.6f, 1.6, -2.4, 2.4, 5, 10);
    glUniformMatrix4fv(projectionUniform, 1, 0, projectionMatrix.pointer());
}



void j3RenderingEngine2::applyOrtho(float maxX, float maxY) const
{
    float a = 1.0f / maxX;
    float b = 1.0f / maxY;
    float ortho[16] = {
        a, 0,  0, 0,
        0, b,  0, 0,
        0, 0, -1, 0,
        0, 0,  0, 1
    };
    
    GLint projectionUniform = glGetUniformLocation(_simpleProgram, "Projection");
    glUniformMatrix4fv(projectionUniform, 1, 0, &ortho[0]);
}



void j3RenderingEngine2::applyRotation(float degrees) const
{
    float radians = degrees * 3.14159f / 180.0f;
    float s = std::sin(radians);
    float c = std::cos(radians);
    float zRotation[16] = {
        c, s, 0, 0,
        -s, c, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    };
    
    GLint modelviewUniform = glGetUniformLocation(_simpleProgram, "Modelview");
    glUniformMatrix4fv(modelviewUniform, 1, 0, &zRotation[0]);
}



void j3RenderingEngine2::render() const
{
    GLuint positionSlot = glGetAttribLocation(_simpleProgram, "Position");
    GLuint colorSlot = glGetAttribLocation(_simpleProgram, "SourceColor");
    
    glClearColor(0.5f, 0.5f, 0.5f, 1);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glEnableVertexAttribArray(positionSlot);
    glEnableVertexAttribArray(colorSlot);
    
    mat4 rotation(_animation.Current.ToMatrix());
    mat4 translation = mat4::translate(0, 0, -7);
    
    // Set the model-view matrix.
    GLint modelviewUniform = glGetUniformLocation(_simpleProgram, "Modelview");
    mat4 modelviewMatrix = rotation * translation;
    glUniformMatrix4fv(modelviewUniform, 1, 0, modelviewMatrix.pointer());
    
    // Draw the cone.
    {
        GLsizei stride = sizeof(Vertex);
        const GLvoid* pCoords = &_cone[0].Position.x;
        const GLvoid* pColors = &_cone[0].Color.x;
        glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, stride, pCoords);
        glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, stride, pColors);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, _cone.size());
    }
    
    // Draw the disk that caps off the base of the cone.
    {
        GLsizei stride = sizeof(Vertex);
        const GLvoid* pCoords = &_disk[0].Position.x;
        const GLvoid* pColors = &_disk[0].Color.x;
        glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, stride, pCoords);
        glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, stride, pColors);
        glDrawArrays(GL_TRIANGLE_FAN, 0, _disk.size());
    }
    
    glDisableVertexAttribArray(positionSlot);
    glDisableVertexAttribArray(colorSlot);
}


float j3RenderingEngine2::rotationDirection() const
{
    float delta = _desiredAngle - _currentAngle;
    if (delta == 0)
        return 0;
    
    bool counterclockwise = ((delta > 0 && delta <= 180) || (delta < -180));
    return counterclockwise ? +1 : -1;
}


void j3RenderingEngine2::updateAnimation(float timeStep)
{
    if (_animation.Current == _animation.End)
        return;
    
    _animation.Elapsed += timeStep;
    if (_animation.Elapsed >= AnimationDuration) {
        _animation.Current = _animation.End;
    } else {
        float mu = _animation.Elapsed / AnimationDuration;
        _animation.Current = _animation.Start.Slerp(mu, _animation.End);
    }
}


void j3RenderingEngine2::onRotate(DeviceOrientation newOrientation)
{
    vec3 direction;
    
    switch (newOrientation) {
        case DeviceOrientationUnknown:
        case DeviceOrientationPortrait:
            direction = vec3(0, 1, 0);
            break;
            
        case DeviceOrientationPortraitUpsideDown:
            direction = vec3(0, -1, 0);
            break;
            
        case DeviceOrientationFaceDown:       
            direction = vec3(0, 0, -1);
            break;
            
        case DeviceOrientationFaceUp:
            direction = vec3(0, 0, 1);
            break;
            
        case DeviceOrientationLandscapeLeft:
            direction = vec3(+1, 0, 0);
            break;
            
        case DeviceOrientationLandscapeRight:
            direction = vec3(-1, 0, 0);
            break;
    }
    
    _animation.Elapsed = 0;
    _animation.Start = _animation.Current = _animation.End;
    _animation.End = Quaternion::CreateFromVectors(vec3(0, 1, 0), direction);
}



GLuint j3RenderingEngine2::buildShader(const char* source, GLenum shaderType) const
{
    GLuint shaderHandle = glCreateShader(shaderType);
    glShaderSource(shaderHandle, 1, &source, 0);
    glCompileShader(shaderHandle);
    
    GLint compileSuccess;
    glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
    
    if (compileSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);
        std::cout << messages;
        exit(1);
    }
    
    return shaderHandle;
}



GLuint j3RenderingEngine2::buildProgram(const char* vertexShaderSource,
                                        const char* fragmentShaderSource) const
{
    GLuint vertexShader = buildShader(vertexShaderSource, GL_VERTEX_SHADER);
    GLuint fragmentShader = buildShader(fragmentShaderSource, GL_FRAGMENT_SHADER);
    
    GLuint programHandle = glCreateProgram();
    glAttachShader(programHandle, vertexShader);
    glAttachShader(programHandle, fragmentShader);
    glLinkProgram(programHandle);
    
    GLint linkSuccess;
    glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetProgramInfoLog(programHandle, sizeof(messages), 0, &messages[0]);
        std::cout << messages;
        exit(1);
    }
    
    return programHandle;
}



void j3RenderingEngine2::onFingerUp(ivec2 location)
{
    
}


void j3RenderingEngine2::onFingerDown(ivec2 location)
{
    
}


void j3RenderingEngine2::onFingerMove(ivec2 oldLocation, ivec2 newLocation)
{
    
}